<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="coinfaces">

        <title>探讨Python的绑定方法</title>

    <link rel="stylesheet" href="../../theme/css/style.css">
    <link rel="stylesheet" href="../../theme/css/pygments.css">



</head>

<body>
    <div class="content">
<h1>探讨Python的绑定方法</h1>    <p>
        under
            <a href="../../tags/python/">python</a>
    </p>
    <p>
        in <a href="../../categories/tech/">tech</a>
    </p>
    <p>Published: 2020-11-29</p>

        <p>Last edit: 2020-11-30</p>

    <p>如下面的代码所示：Python中定义实例方法时第一个参数是实例对象，而调用实例方法时则不需要传这个参数。因为从定义到调用，中间的某个步骤将实例对象和实例方法的第一个参数绑定了起来，这种绑定遵循哪些规则？本文试图通过一些试验来探讨这个问题。</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">spam</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">):</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;</span><span class="si">{}</span><span class="s1">, </span><span class="si">{}</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span> <span class="n">a</span><span class="p">))</span>


<span class="n">foo</span> <span class="o">=</span> <span class="n">Foo</span><span class="p">()</span>
<span class="n">foo</span><span class="o">.</span><span class="n">spam</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># Foo, 1</span>
</code></pre></div>


<h4>规则1、只能绑定类属性</h4>
<p>Python中万物皆对象，类也不例外，在类的代码块中定义的变量和函数自动变成类对象的属性。既然只是属性，也可以直接通过属性赋值的形式定义，如下面的代码所示：</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">pass</span>


<span class="k">def</span> <span class="nf">spam</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;</span><span class="si">{}</span><span class="s1">, </span><span class="si">{}</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span> <span class="n">a</span><span class="p">))</span>


<span class="n">foo</span> <span class="o">=</span> <span class="n">Foo</span><span class="p">()</span>
<span class="n">Foo</span><span class="o">.</span><span class="n">spam</span> <span class="o">=</span> <span class="n">spam</span>  <span class="c1"># 在foo.spam操作之前完成赋值即可</span>
<span class="n">foo</span><span class="o">.</span><span class="n">spam</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># Foo, 1</span>
</code></pre></div>


<p>实例属性不会产生绑定操作，这里会提示参数数量不足</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">eggs</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;eggs </span><span class="si">{}</span><span class="s1">&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">b</span><span class="p">))</span>


<span class="n">foo</span><span class="o">.</span><span class="n">eggs</span> <span class="o">=</span> <span class="n">eggs</span>
<span class="n">foo</span><span class="o">.</span><span class="n">eggs</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># TypeError: eggs() takes exactly 2 arguments (1 given)</span>
</code></pre></div>


<h4>规则2、必须通过实例的属性来调用这个方法</h4>
<p>下面这段代码Python2和Python3会提示不同的错误，这是由于Python2和Python3对类属性的不同处理造成的。</p>
<div class="highlight"><pre><span></span><code><span class="n">Foo</span><span class="o">.</span><span class="n">spam</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div>


<p>Python2中类属性<code>Foo.spam</code>是未绑定方法（unbound method），有2个意思：</p>
<ol>
<li>没有绑定任何<code>Foo</code>的实例</li>
<li>调用时第一个参数必须是<code>Foo</code>的实例。</li>
</ol>
<div class="highlight"><pre><span></span><code><span class="err">&gt;&gt;&gt; Foo.spam</span>
<span class="err">&lt;unbound method Foo.spam&gt;</span>
</code></pre></div>


<p>所以它提示的错误是：</p>
<blockquote>
<p>TypeError: unbound method spam() must be called with Foo instance as first argument (got int instance instead)</p>
</blockquote>
<p>Python3没有未绑定方法，<code>Foo.spam</code>是普通函数：</p>
<div class="highlight"><pre><span></span><code><span class="err">&gt;&gt;&gt; Foo.spam</span>
<span class="err">&lt;function spam at 0x7efff82d9c80&gt;</span>
</code></pre></div>


<p>所以这里提示的错误是：</p>
<blockquote>
<p>TypeError: spam() missing 1 required positional argument: 'a'</p>
</blockquote>
<p>通过类属性调用的正确方式是：</p>
<div class="highlight"><pre><span></span><code><span class="n">Foo</span><span class="o">.</span><span class="n">spam</span><span class="p">(</span><span class="n">foo</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>  <span class="c1"># Foo, 1</span>
</code></pre></div>


<h4>规则3、通过实例属性取得的值即是绑定方法</h4>
<p>从上面的例子可以看出，Python2中，通过<code>Foo.spam</code>获取的属性不等同于赋值给<code>Foo</code>的<code>spam</code>属性的值。类似的，通过<code>Foo</code>的实例<code>foo</code>取得的<code>spam</code>属性是更加不一样的东西，它是绑定方法（bound method）。</p>
<div class="highlight"><pre><span></span><code><span class="err">&gt;&gt;&gt; foo.spam</span>
<span class="err">&lt;bound method Foo.spam of &lt;__main__.Foo object at 0x7f8f62e7b810&gt;&gt;</span>
</code></pre></div>


<p>因此，可以将<code>foo.spam(1)</code>拆成2步：</p>
<div class="highlight"><pre><span></span><code><span class="n">foo_spam</span> <span class="o">=</span> <span class="n">foo</span><span class="o">.</span><span class="n">spam</span>
<span class="n">foo_spam</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># Foo, 1</span>
</code></pre></div>


<p>作为对比，可以看一个JavaScript的例子</p>
<div class="highlight"><pre><span></span><code><span class="kd">function</span> <span class="nx">Foo</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="s1">&#39;foo&#39;</span>
<span class="p">}</span>

<span class="nx">Foo</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">spam</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">+</span> <span class="s1">&#39;, &#39;</span> <span class="o">+</span> <span class="nx">a</span><span class="p">)</span>
<span class="p">}</span>

<span class="kd">var</span> <span class="nx">spam</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Foo</span><span class="p">().</span><span class="nx">spam</span>
<span class="nx">spam</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1">// undefined, 1</span>
</code></pre></div>


<p>要达到和Python代码相同的效果，需要调用<code>Function.prototype.bind</code>：</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span> <span class="nx">foo</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Foo</span><span class="p">()</span>
<span class="kd">var</span> <span class="nx">foo_spam</span> <span class="o">=</span> <span class="nx">foo</span><span class="p">.</span><span class="nx">spam</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">foo</span><span class="p">)</span>
<span class="nx">foo_spam</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1">// foo, 1</span>
</code></pre></div>


<h4>规则4、已绑定的操作不可撤销</h4>
<p>绑定了实例的方法不能再绑定其它实例：</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Baz</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">pass</span>


<span class="n">Baz</span><span class="o">.</span><span class="n">spam</span> <span class="o">=</span> <span class="n">foo</span><span class="o">.</span><span class="n">spam</span>
<span class="n">baz</span> <span class="o">=</span> <span class="n">Baz</span><span class="p">()</span>
<span class="n">baz</span><span class="o">.</span><span class="n">spam</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>  <span class="c1"># Foo, 4</span>
</code></pre></div>


<p>下面的这段代码，Python2和Python3的运行结果也不一样。</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Baz</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">pass</span>


<span class="n">Baz</span><span class="o">.</span><span class="n">spam</span> <span class="o">=</span> <span class="n">Foo</span><span class="o">.</span><span class="n">spam</span>
<span class="n">baz</span> <span class="o">=</span> <span class="n">Baz</span><span class="p">()</span>
<span class="n">baz</span><span class="o">.</span><span class="n">spam</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
</code></pre></div>


<p>Python3中类属性<code>Foo.spam</code>仍是普通函数，因此这段代码可以顺利运行，输出：</p>
<blockquote>
<p>Baz, 4</p>
</blockquote>
<p>而Python2的<code>Foo.spam</code>其实绑定了<code>Foo</code>类，不再能接受<code>Foo</code>实例之外的值作为第1参数。即使将其赋值给<code>Baz</code>类的<code>spam</code>属性，仍保持和<code>Foo</code>绑定，因此这段代码会报错：</p>
<blockquote>
<p>unbound method spam() must be called with Foo instance as first argument (got int instance instead)</p>
</blockquote>
<p>但如果将<code>Foo.spam</code>赋值给<code>Foo</code>的另一个属性，它也能顺利绑定：</p>
<div class="highlight"><pre><span></span><code><span class="n">Foo</span><span class="o">.</span><span class="n">rice</span> <span class="o">=</span> <span class="n">Foo</span><span class="o">.</span><span class="n">spam</span>

<span class="n">foo</span><span class="o">.</span><span class="n">rice</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div>


<p>虽然<code>Foo.rice</code>和<code>foo.rice</code>仍被当作<code>Foo.spam</code>：</p>
<div class="highlight"><pre><span></span><code><span class="err">&gt;&gt;&gt; Foo.rice</span>
<span class="err">&lt;unbound method Foo.spam&gt;</span>
<span class="err">&gt;&gt;&gt; foo.rice</span>
<span class="err">&lt;bound method Foo.spam of &lt;__main__.Foo object at 0x7f8f62e7b810&gt;&gt;</span>
</code></pre></div>


<hr>
<p>作为Python2的未绑定方法的一个极端例子，可以考虑下面一个定义得很糟糕的方法：</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Foo</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">bar</span><span class="p">():</span>
        <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Calling bar&#39;</span><span class="p">)</span>
</code></pre></div>


<p>在Python2中怎么调用这个方法都会报错：</p>
<div class="highlight"><pre><span></span><code><span class="n">Foo</span><span class="o">.</span><span class="n">bar</span><span class="p">()</span>  <span class="c1"># TypeError: unbound method bar() must be called with Foo  instance as first argument (got nothing instead)</span>
<span class="n">Foo</span><span class="p">()</span><span class="o">.</span><span class="n">bar</span><span class="p">()</span>  <span class="c1"># TypeError: bar() takes no arguments (1 given)</span>
</code></pre></div>


<p>Python3可以通过类属性来调用这个函数：</p>
<div class="highlight"><pre><span></span><code><span class="n">Foo</span><span class="o">.</span><span class="n">bar</span><span class="p">()</span>  <span class="c1"># Calling bar</span>
</code></pre></div>
    </div>
<nav class="nav sidebar">
    <div class="siteinfo">
        <div class="sitename"><a href="../..">coinfaces</a></div>
        <div class="tagline">Another Side of Life</div>
    </div>

        <a href="../../categories/">categories</a>
        <a href="../..">&larr; Home</a>
</nav><footer class="footer">
    <span>
        &copy; jpli &middot;
        under <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA 4.0</a>
    </span>
    |
    <span>
        Powered by:
        <a href="http://blog.getpelican.com/">Pelican</a>
    </span>
</footer></body>
</html>